/**
 * @file ftpgetfiles1.cpp
 * @author your name (you@domain.com)
 * @brief
 * @version 0.1
 * @date 2024-05-09
 *
 * @copyright Copyright (c) 2024
 *
 */
#include <iostream>
#include "../../public/_ftp.h"
#include "../../public/_public.h"

void help(); // help文档
// 参数解析，放到一个结构体中
struct st_arg
{
    char host[31];           // 远程服务器的IP和端口。
    int mode;                // 传输模式，1-被动模式，2-主动模式，缺省采用被动模式。
    char username[31];       // 远程服务器ftp的用户名。
    char password[31];       // 远程服务器ftp的密码。
    char remotepath[301];    // 远程服务器存放文件的目录。
    char localpath[301];     // 本地文件存放的目录。
    char matchname[101];     // 待下载文件匹配的规则。
    char listfilename[301];  // 存放文件名 的文件的名字
    int ptype;               // 下载后服务端文件的处理方式：1-什么也不做；2-删除；3-备份。
    char remotepathbak[301]; // 下载后服务端文件的备份目录。
    char okfilename[301];    // 已下载成功文件名清单。
    bool checkmtime;         // 是否需要检查服务端文件的时间，true-需要，false-不需要，缺省为false。

} starg;

// 文件信息结构体
struct st_fileinfo
{
    char filename[301]; // 文件名。不存储路径信息
    char mtime[21];     // 文件时间。
};

CLogFile logfile; // 日志
Cftp ftp;
CFile file;
CFile file1;
vector<st_fileinfo> vlistfile1; // 记录上一次操作完成后，有哪些文件已经下载完毕并且就录了他们的版本
vector<st_fileinfo> vlistfile2; // 记录当前操作时，存在的文件与版本
vector<st_fileinfo> vlistfile3; // 当前不需要更新的文件
vector<st_fileinfo> vlistfile4; // 当前需要更新到客户端的文件
vector<st_fileinfo> vlistfile0; // 当前需要更新到客户端的文件
struct st_fileinfo stfileinfo;
struct st_fileinfo stfileinfo1; // 存储okfile中的每一个文件

void _xmltoarg(char *strxmlbuffer);
bool _ftpgetfiles();
bool LoadListFile();
bool getNewFile();
bool isAppend(st_fileinfo fileInV2);
bool isModify(st_fileinfo fileInV2);
void modifyV1();
void checkV2AndV1();

void print(char *c)
{
    printf("%s", c);
}

int main(int argc, char *argv[]) // argv[2]
{
    if (argc != 3)
    {
        help();
        return -1;
    }
    // 打开日志
    if (false == logfile.Open(argv[1]))
    {
        printf("打开日志失败\n");
        return -1;
    }
    _xmltoarg(argv[2]);

    // 处理缺省参数缺省参数
    if (starg.mode != 2)
    {
        starg.mode = 1;
    }
    // 默认话
    if (starg.ptype == 0)
    {
        starg.ptype = 2;
    }
    if (starg.remotepathbak == 0 && starg.ptype == 3)
    {
        printf("模式是备份，但是没有提供备份目录，或者备份目录不存在");
        return -1;
    }
    if (_ftpgetfiles() == false)
    {
        return -1;
    }

    return 0;
}
void help()
{
    printf("\n");
    printf("Using:/project/tools1/bin/ftpgetfiles logfilename xmlbuffer\n\n");

    printf("Sample:/project/tools1/bin/procctl 30 /project/tools1/bin/ftpgetfiles /log/idc/ftpgetfiles_surfdata.log \"<host>127.0.0.1:21</host><mode>1</mode><username>wucz</username><password>wuczpwd</password><localpath>/idcdata/surfdata</localpath><remotepath>/tmp/idc/surfdata</remotepath><matchname>SURF_ZH*.XML,SURF_ZH*.CSV</matchname>\"\n\n\n");

    printf("本程序是通用的功能模块，用于把远程ftp服务器的文件下载到本地目录。\n");
    printf("logfilename是本程序运行的日志文件。\n");
    printf("xmlbuffer为文件下载的参数，如下：\n");
    printf("<host>127.0.0.1:21</host> 远程服务器的IP和端口。\n");
    printf("<mode>1</mode> 传输模式，1-被动模式，2-主动模式，缺省采用被动模式。\n");
    printf("<username>wucz</username> 远程服务器ftp的用户名。\n");
    printf("<password>wuczpwd</password> 远程服务器ftp的密码。\n");
    printf("<remotepath>/tmp/idc/surfdata</remotepath> 远程服务器存放文件的目录。\n");
    printf("<localpath>/idcdata/surfdata</localpath> 本地文件存放的目录。\n");
    printf("<matchname>SURF_ZH*.XML,SURF_ZH*.CSV</matchname> 待下载文件匹配的规则。"
           "不匹配的文件不会被下载，本字段尽可能设置精确，不建议用*匹配全部的文件。\n");
    printf("<listfilename>/idcdata/ftplist/ftpgetfiles_surfdata.list</listfilename> 下载前列出服务器文件名的文件。\n");
    printf("<ptype>2</ptype> 文件下载成功后，远程服务端文件的处理方式：1-什么也不做；2-删除；3-备份，如果为3，还要指定备份的目录。\n");
    printf("<okfilename>/idcdata/ftplist/ftpgetfiles_surfdata.xml</okfilename> 已下载成功文件名清单，此参数只有当ptype=1时才有效。\n");
    printf("<remotepathbak>/tmp/idc/surfdatabak</remotepathbak> 文件下载成功后，服务端文件的备份目录，此参数只有当ptype=3时才有效。\n");
    printf("<checkmtime>true</checkmtime> 是否需要检查服务端文件的时间，true-需要，false-不需要，此参数只有当ptype=1时才有效，缺省为false。\n");
}
//"./ftpgetfiles /wanyingxing/project/log/ftpget.log
//<host>192.168.9.242</host><mode>1</mode><username>root</username><password>Wanyingxing0</password><remotepath>/wanyingxing/project/surfdata</remotepath><localpath>/wanyingxing/project/idcdata/surfdata</localpath><matchname>SURF_ZH*.XML,SURF_ZH*.CSV</matchname><listfilename>/wanyingxing/project/idcdata/ftplist/ftpgetfiles_surfdata.list</listfilename>"

void _xmltoarg(char *strxmlbuffer)
{

    // xml
    // bool GetXMLBuffer(const char *xmlbuffer,const char *fieldname,char *value,const int ilen=0);
    memset(&starg, 0, sizeof(st_arg));
    if (false == GetXMLBuffer(strxmlbuffer, "host", starg.host))
    {
        printf("解析host失败\n");
        logfile.Write("解析host失败\n");
    }
    if (false == GetXMLBuffer(strxmlbuffer, "mode", &starg.mode))
    {
        printf("解析mode失败\n");
        logfile.Write("解析mode失败\n");
    }
    if (false == GetXMLBuffer(strxmlbuffer, "username", starg.username))
    {
        printf("解析username失败\n");
        logfile.Write("解析username失败\n");
    }
    if (false == GetXMLBuffer(strxmlbuffer, "password", starg.password))
    {
        printf("解析password失败\n");
        logfile.Write("解析password失败\n");
    }
    if (false == GetXMLBuffer(strxmlbuffer, "remotepath", starg.remotepath))
    {
        printf("解析remotepath失败\n");
        logfile.Write("解析remotepath失败\n");
    }
    if (false == GetXMLBuffer(strxmlbuffer, "localpath", starg.localpath))
    {
        printf("解析localpath失败\n");
        logfile.Write("解析localpath失败\n");
    }
    if (false == GetXMLBuffer(strxmlbuffer, "matchname", starg.matchname))
    {
        printf("解析matchname失败\n");
        logfile.Write("解析matchname失败\n");
    }
    if (false == GetXMLBuffer(strxmlbuffer, "listfilename", starg.listfilename))
    {
        printf("解析listfilename失败\n");
        logfile.Write("解析listfilename失败\n");
    }

    // 解析处理命令
    if (false == GetXMLBuffer(strxmlbuffer, "ptype", &starg.ptype))
    {
        printf("解析ptype失败\n");
        logfile.Write("解析ptype\n");
    }
    // 解析备份目录
    // 这是可选变量，只有在ptype=3,才会有用
    if (false == GetXMLBuffer(strxmlbuffer, "remotepathbak", starg.remotepathbak))
    {
        printf("解析bakdir失败\n");
        logfile.Write("解析bakdir失败\n");
    }
    if (false == GetXMLBuffer(strxmlbuffer, "okfilename", starg.okfilename, 300))
    {
        printf("解析okfilename失败\n");
        logfile.Write("解析okfilename失败\n");
    } // 已下载成功文件名清单。
    if ((starg.ptype == 1) && (strlen(starg.okfilename) == 0))
    {
        logfile.Write("okfilename is null.\n");
    }
    // 是否需要检查服务端文件的时间，true-需要，false-不需要，此参数只有当ptype=1时才有效，缺省为false。
    if (false == GetXMLBuffer(strxmlbuffer, "checkmtime", &starg.checkmtime))
    {
        printf("解析checkmtime失败\n");
        logfile.Write("解析checkmtime失败\n");
    }
    printf("解析参数成功\n");
}
bool _ftpgetfiles()
{
    // 连接服务器   bool login(const char *host,const char *username,const char *password,const int imode=FTPLIB_PASSIVE);
    if (false == ftp.login(starg.host, starg.username, starg.password, starg.mode))
    {
        logfile.Write("登陆失败\n");
        printf("登陆失败\n");
        return false;
    }
    // 进入服务器目录
    if (ftp.chdir(starg.remotepath) == false)
    {
        logfile.Write("进入目录失败\n");
        printf("进入目录失败\n");
        return false;
    }
    // 读取目录下的文件，存放到一个本地文件中   bool nlist(const char *remotedir,const char *listfilename);
    if (false == ftp.nlist(".", starg.listfilename))
    {
        logfile.Write("访问文件夹失败\n");
        printf("访问文件夹失败\n");
        return false;
    }

    if (LoadListFile() == false)
    {
        logfile.Write("加载文件失败\n");
        printf("加载文件失败\n");
        return false;
    }

    // 下载文件操作
    getNewFile();

    return true;
}

// 读取文件时，不仅仅读取了文件的名
// 文件修改时间
// 只读取了文件名
// 为了完善，要一个结构体，及存储文件，也要存储文件时间，vector存储哪些文件信息即可

bool LoadListFile()
{
    // 过滤并且村发给到容器,打开一个文件，都这个文件，检查是否符合匹配规则，存放到容器中
    file.Open(starg.listfilename, "r");

    // 初始化
    memset(&stfileinfo, 0, sizeof(st_fileinfo));
    while (true)
    {
        // 循环读文件一行
        if (false == file.Fgets(stfileinfo.filename, 300, true))
        {
            break;
        }
        if (MatchStr(stfileinfo.filename, starg.matchname) == false)
            continue; // 如果文件不符合匹配规则

        // 读时间和文件名，写文件信息到容器中
        if (ftp.mtime(stfileinfo.filename) == false)
        {
            logfile.Write("ftp.mtime(%s) failed.\n", stfileinfo.filename);
            return false;
        }
        strcpy(stfileinfo.mtime, ftp.m_mtime);
        vlistfile2.push_back(stfileinfo); // 这里把文件信息存放计入，这里存放的就没有路径信息，否在待会逻辑说不通
        memset(&stfileinfo, 0, sizeof(st_fileinfo));
    }

    // 过滤
    if (starg.ptype == 1 && starg.checkmtime == true)
    {
        // 读取存储上一次下载后的，记录已经下载完成的文件信息，读取V1，加载OKfile文件，然后，读取okfile文件的信息,使用xml格式
        file1.Open(starg.okfilename, "r");
        char okfileXmlInfo[301];
        memset(okfileXmlInfo, 0, 301);
        while (true)
        {

            if (false == file1.Fgets(okfileXmlInfo, 300, true))
            {
                // okfile读取结束
                break;
            }

            // 解析参数
            GetXMLBuffer(okfileXmlInfo, "filename", stfileinfo1.filename);
            GetXMLBuffer(okfileXmlInfo, "mtime", stfileinfo1.mtime);
            if (MatchStr(stfileinfo1.filename, starg.matchname) == false)
                continue; // 如果文件不符合匹配规则
            vlistfile1.push_back(stfileinfo1); // 这里把文件信息存放计入，这里存放的就没有路径信息，否在待会逻辑说不通
            memset(&stfileinfo1, 0, sizeof(st_fileinfo));
        }
        // 检查V2和v1,生成v3和v4
        checkV2AndV1();
        modifyV1(); // 覆盖v1
        vlistfile0 = vlistfile4;
    }
    // 下载所有的文件
    else
    {
        vlistfile0 = vlistfile2;
        // 用V2去覆盖v1
        for (int i = 0; i < vlistfile2.size(); i++)
        {
            vlistfile1.push_back(vlistfile2[i]);
        }
        file1.Open(starg.okfilename, "w");

        for (int i = 0; i < vlistfile1.size(); i++)
        {
            file1.Fprintf("<filename>%s</filename><mtime>%s</mtime>\n", vlistfile1[i].filename, vlistfile1[i].mtime);
        }
        //------------------------------------------------------------------------------------------------------//这里有一个问题
        file1.Close();
    }

    return true;
}

// 检查当前的这个文件是不是新增
bool isAppend(st_fileinfo fileInV2)
{
    for (int i = 0; i < vlistfile1.size(); i++)
    {
        if (strcmp(vlistfile1[i].filename, fileInV2.filename) == 0)
        {
            return false;
        }
    }
    return true;
}

// 检查当前这个文件是不是已经发生修改，同时存在该文件
bool isModify(st_fileinfo fileInV2)
{
    int index = 0;
    for (int i = 0; i < vlistfile1.size(); i++)
    {
        if (strcmp(vlistfile1[i].filename, fileInV2.filename) == 0)
        {
            break;
        }
        index++;
    }
    if (strcmp(vlistfile1[index].mtime, fileInV2.mtime) == 0)
    {
        return false;
    }
    else
    {
        return true;
    }
}

// 还要写入到文件中，下一次使用
void modifyV1()
{
    vlistfile1.clear();
    for (int i = 0; i < vlistfile3.size(); i++)
    {
        vlistfile1.push_back(vlistfile3[i]);
    }
    for (int i = 0; i < vlistfile4.size(); i++)
    {
        vlistfile1.push_back(vlistfile4[i]);
    }

    // 写入到文件中，打开文件
    file1.Open(starg.okfilename, "w");

    for (int i = 0; i < vlistfile1.size(); i++)
    {
        file1.Fprintf("<filename>%s</filename><mtime>%s</mtime>\n", vlistfile1[i].filename, vlistfile1[i].mtime);
    }
    file1.Close();
}

void checkV2AndV1()
{
    // 比较V1和V2,存放数据到v3和V4
    for (int i = 0; i < vlistfile2.size(); i++)
    {
        if (isAppend(vlistfile2[i]))
        {
            vlistfile4.push_back(vlistfile2[i]);
        }
        else
        {
            if (isModify(vlistfile2[i]))
            {
                vlistfile4.push_back(vlistfile2[i]);
            }
            else
            {
                vlistfile3.push_back(vlistfile2[i]);
            }
        }
    }
}

// 下载文件的逻辑
bool getNewFile()
{
    char strremotefilename[301], strlocalfilename[301];

    // 存储新增的，或者是修改的
    for (int ii = 0; ii < vlistfile0.size(); ii++)
    {
        // 拼接成为全路径，vectpor中存放的不是全路径的文件名，仅仅是文件名
        SNPRINTF(strremotefilename, sizeof(strremotefilename), 300, "%s/%s", starg.remotepath, vlistfile0[ii].filename);
        SNPRINTF(strlocalfilename, sizeof(strlocalfilename), 300, "%s/%s", starg.localpath, vlistfile0[ii].filename);

        // 调用ftp.get()方法从服务器下载文件。
        logfile.Write("get %s ...", strremotefilename);

        if (ftp.get(strremotefilename, strlocalfilename) == false)
        {
            logfile.WriteEx("failed.\n");
            break;
        }
        else
        {
            // 加入功能模式:
            if (starg.ptype == 2)
            {
                // 删除文件
                if (ftp.ftpdelete(strremotefilename) == false)
                {
                    logfile.WriteEx("delete failed.\n");
                    return false;
                }
            }
            else if (starg.ptype == 3)
            {
                // 备份文件
                // 拼接备份文件的全路径
                char strremotebakfilename[301];
                SNPRINTF(strremotebakfilename, sizeof(strremotebakfilename), 300, "%s/%s", starg.remotepathbak, vlistfile0[ii].filename);
                if (ftp.ftprename(strremotefilename, strremotebakfilename) == false)
                {
                    logfile.WriteEx("bak failed.\n");
                    return false;
                }
            }
        }

        logfile.WriteEx("ok.\n");
    }
}